package com.chaosj.skill.zipkin.context

import brave.Tracing
import brave.sampler.CountingSampler
import brave.sampler.Sampler
import brave.servlet.TracingFilter
import com.chaosj.skill.zipkin.annotations.DefaultOgnlSpanParser
import com.chaosj.skill.zipkin.annotations.DelegateSpanParser
import com.chaosj.skill.zipkin.annotations.ISpanParser
import com.chaosj.skill.zipkin.interceptor.HttpUrlConnectionInterceptor
import com.chaosj.skill.zipkin.interceptor.OkhttpTraceInterceptor
import com.chaosj.skill.zipkin.proxy.ProxyInvocationHandler
import com.google.common.base.Preconditions
import com.google.common.base.Strings
import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
import com.google.common.reflect.Reflection
import okhttp3.OkHttpClient
import zipkin2.Span
import zipkin2.reporter.AsyncReporter
import zipkin2.reporter.Reporter
import zipkin2.reporter.Sender
import zipkin2.reporter.okhttp3.OkHttpSender

import javax.servlet.Filter

/**
 * @Description:
 * @author: caoyangjie
 * @date: 2019年12月2019/12/6日 09:18
 * @Copyright: © 赢时胜
 */
class ApplicationContext {
    static final String POST_API_V2_SPANS = "%s/api/v2/spans"
    static Cache<String,Object> iocContext = CacheBuilder.newBuilder().build()

    static <T> T getBean(Class<T> clazz){
        T instance = (T) iocContext.getIfPresent(clazz.getName())
        return Optional.ofNullable(instance).orElseThrow({new RuntimeException("请先执行Builder中的initialization方法进行上下文初始化操作!")})
    }

    private static Sender sender(String zipKinServerUri){
        OkHttpSender sender = OkHttpSender
                .newBuilder()
                .endpoint(zipKinServerUri).build()
        return sender
    }

    private static Reporter<Span> reporter(Sender sender){
        return AsyncReporter
                .builder(sender)
                .build()
    }

    private static Tracing tracing(Reporter reporter, String localServiceName){
        return Tracing.newBuilder()
                .localServiceName(localServiceName)
                .sampler(getBean(Sampler.class))
                .spanReporter(reporter)
                .build()
    }

    private static Filter filter(Tracing tracing){
        return TracingFilter.create(tracing)
    }

    private static Sampler sampler(float sampler){
        return CountingSampler.create(sampler)
    }

    private static ISpanParser parser(){
        return new DelegateSpanParser().setParsers(
                // 设置一个默认 OGNL的 span 解析器
                new DefaultOgnlSpanParser()
        )
    }

    /**
     * 根据类名,类实例 创建一个 代理实例对象
     * @param clazz
     * @param delegate
     * @param <T>
     * @return
     */
    static <T> T newProxyInstance(Class<T> clazz,T delegate){
        T proxyInstance = null
        try{
            proxyInstance = getBean(clazz)
            return proxyInstance
        }catch(Exception e){
        }
        proxyInstance = Reflection.newProxy(clazz,new ProxyInvocationHandler<T>(delegate))
        iocContext.put(clazz.getName(),proxyInstance)
        return proxyInstance
    }

    static class Builder{
        private String zipKinServerURI

        private String localServiceName

        private float sampler

        Builder(String zipKinServerURI, String localServiceName,float sampler) {
            this.zipKinServerURI = Strings.lenientFormat(POST_API_V2_SPANS,zipKinServerURI)
            this.localServiceName = localServiceName
            this.sampler = sampler
        }

        void initialization(){
            Preconditions.checkArgument(sampler>=0.01&&sampler<=1,"sampler采样取值范围[ 0.01-1 ]!")
            Preconditions.checkArgument(!Strings.isNullOrEmpty(zipKinServerURI),"zipKinServerURI不允许为空!")
            Preconditions.checkArgument(!Strings.isNullOrEmpty(localServiceName),"localServiceName不允许为空!")
            iocContext.put(Sampler.class.getName(),sampler(sampler))
            iocContext.put(Sender.class.getName(),sender(zipKinServerURI))
            iocContext.put(Reporter.class.getName(),reporter(getBean(Sender.class)))
            iocContext.put(Tracing.class.getName(),tracing(getBean(Reporter.class),localServiceName))
            iocContext.put(Filter.class.getName(),filter(getBean(Tracing.class)))
            iocContext.put(ISpanParser.class.getName(),parser())
            iocContext.put(OkHttpClient.class.getName(),new OkHttpClient.Builder().addInterceptor(new OkhttpTraceInterceptor()).build())
            iocContext.put(HttpURLConnection.class.getName(),new HttpUrlConnectionInterceptor())
            iocContext.put(Builder.class.getName(),this)
        }

        String getLocalServiceName() {
            return localServiceName
        }
    }
}
